/*++

Copyright (c) Microsoft Corporation. 
 
This source code is subject to terms and conditions of the Microsoft
Public License. A copy of the license can be found in the License.txt
file at the root of this distribution. By using this source code in 
any fashion, you are agreeing to be bound by the terms of the Microsoft
Public License.
 
You must not remove this notice, or any other, from this software.

Module Name:

    RS232Target.cpp

Abstract:

    This module contains the code that supports the asynchronous communication
    between the sensor device and the driver.
--*/

#include <stdio.h>
#include <tchar.h>

// This driver is entirely user-mode
__user_code;

#include <atlbase.h>
#include <atlcom.h>
#include <atlcoll.h>
#include <atlstr.h>
#include <strsafe.h>

// Added below to try and resolve compiler error in RS232Target.h
#include <wudfddi.h> 


#include "SensorBaseDriver.h" //Required to invoke ProcessReadData
#include "sensorsclassextension.h" //Required for state-change events
#include "internal.h"
#include "RS232Target.h"
#include "RS232Target.tmh"

RS232Target::RS232Target() :
    m_cRef(1), 
    m_pBaseDriver(NULL),
    m_bDataRcvd(false),
    m_cleanupgroup(NULL),
    m_pool(NULL),
    m_timer(NULL),
    m_state(SENSOR_STATE_INITIALIZING)
{
    ZeroMemory(&m_FileDueTime, sizeof(m_FileDueTime));
}

RS232Target::~RS232Target()
{
    Delete();
}

ULONG __stdcall RS232Target::AddRef()
{
    InterlockedIncrement((long*) &m_cRef);
    return m_cRef;
}

ULONG __stdcall RS232Target::Release()
{
    ULONG ulRefCount = m_cRef - 1;

    if (InterlockedDecrement((long*) &m_cRef) == 0)
    {
        delete this;
        return 0;
    }
    return ulRefCount;
}

HRESULT __stdcall RS232Target::QueryInterface(
    REFIID riid,
    void** ppv)
{
    HRESULT hr = S_OK;

    if(riid == IID_IUnknown)
    {
        *ppv = static_cast<IUnknown*>(this);
        AddRef();
    }
    if(riid == __uuidof(IRequestCallbackRequestCompletion))
    {
        *ppv = static_cast<IRequestCallbackRequestCompletion*>(this);
        AddRef();
    }
    else
    {
        *ppv = NULL;
        hr = E_NOINTERFACE;
    }
    return hr;
}



/**
 * This method is called to initialize the RS232 I/O Target
 */
HRESULT RS232Target::Create(SensorBaseDriver* pBaseDriver,
                            IWDFDevice*    pDevice, 
                            HANDLE         hRS232Port)
{
    CComPtr<IWDFFileHandleTargetFactory> pFileHandleTargetFactory;

    HRESULT hr = S_OK;

    if (pDevice == NULL)
    {
        hr = E_POINTER;
        return hr;
    }

    m_pWDFDevice = pDevice;
    m_pBaseDriver = pBaseDriver;

    hr = m_pWDFDevice->QueryInterface(IID_PPV_ARGS(&pFileHandleTargetFactory));

    if (hr == S_OK)
    {
        hr = pFileHandleTargetFactory->CreateFileHandleTarget(hRS232Port, &m_pFileTarget);
    }

    if (hr == S_OK)
    {
		DoTraceMessage(TRACE_FLAG_RS232, 
			"%!FUNC! Created win32 I/O target %p for handle %p", m_pFileTarget, hRS232Port);
    }

    return hr;
}

/**
 * This method is called to remove the RS232 I/O Target object
 * and do any cleanup
 */
void RS232Target::Delete()
{
    if (m_pFileTarget)
    {
		DoTraceMessage(TRACE_FLAG_RS232, 
			"%!FUNC! Deleted win32 I/O target %p", m_pFileTarget);
        m_pFileTarget = NULL;
    }

    if (m_pWDFDevice)
    {
        m_pWDFDevice = NULL;
    }

    if (m_pBaseDriver)
    {
        m_pBaseDriver = NULL;
    }
}

/**
 * This method is called to start the RS232 I/O Target after it 
 * it has been created
 */
HRESULT RS232Target::Start()
{
    CComPtr<IWDFIoTargetStateManagement> pStateMgmt;

    HRESULT hr                      = S_OK;


    if (m_pFileTarget)
    {
        hr = m_pFileTarget->QueryInterface(IID_PPV_ARGS(&pStateMgmt));

        if (hr == S_OK)
        {
            hr = pStateMgmt->Start();
        }

        if (hr == S_OK && IsReady())
        {
			SetSensorState(SENSOR_STATE_READY);

			DoTraceMessage(TRACE_FLAG_RS232,
				"%!FUNC! I/O target ready. Sending read request");

            hr = SendReadRequest();  
        }
    }
    return hr;
}

/**
 * This method is called to stop the RS232 I/O Target 
 * if it is currently running
 */
HRESULT RS232Target::Stop()
{
    CComPtr<IWDFIoTargetStateManagement> pStateMgmt;

    HRESULT hr = S_OK;

    // Stop the target only if it is started. 
    if (m_pFileTarget && (WdfIoTargetStarted == GetState()))
    {
        hr = m_pFileTarget->QueryInterface(IID_PPV_ARGS(&pStateMgmt));
        if (hr == S_OK)
        {
            // Stop the target, and cancel sent I/O
            hr = pStateMgmt->Stop(WdfIoTargetCancelSentIo);
        }

        if (hr == S_OK)
        {
        }
    }
    return hr;
}

/**
 * This method is called to return the current state of the RS232 I/O Target
 */
WDF_IO_TARGET_STATE RS232Target::GetState()
{
    CComPtr<IWDFIoTargetStateManagement> pStateMgmt;

    WDF_IO_TARGET_STATE State = WdfIoTargetStateUndefined;
    HRESULT             hr    = S_OK;

    if (m_pFileTarget)
    {
        hr = m_pFileTarget->QueryInterface(IID_PPV_ARGS(&pStateMgmt));
      
        if (hr == S_OK)
        {
            State = pStateMgmt->GetState();
        }
    }
    return State;
}


/**
 * This method returns TRUE is the target is ready to receive requests
 */
BOOL RS232Target::IsReady()
{
    if (WdfIoTargetStarted == GetState())
    {
        return TRUE;
    }
    return FALSE;
}


/**
 * This method is called to dispatch an asynchronous read request to the RS232 I/O Target
 * with a completion callback
 */
HRESULT RS232Target::SendReadRequest()
{
    CComPtr<IWDFDriver>                        pWdfDriver;
    CComPtr<IWDFFile>                          pWdfFile;
    CComPtr<IWDFMemory>                        pWdfBuffer;
    CComPtr<IWDFIoRequest>                     pWdfReadRequest;
    CComPtr<IRequestCallbackRequestCompletion> pCompletionCallback;  

    HRESULT hr = S_OK;

    if (m_pWDFDevice == NULL || m_pFileTarget == NULL)
    {
        hr = HRESULT_FROM_WIN32(ERROR_NOT_READY);
        //CHECK_HR(hr, "Device is not ready to receive read requests");
        return hr;
    }

    m_pWDFDevice->GetDriver(&pWdfDriver);

    ZeroMemory((void*)m_pReadBuffer, sizeof(m_pReadBuffer));

    // Create the WDF memory buffer
    hr = pWdfDriver->CreatePreallocatedWdfMemory(m_pReadBuffer, 
                                                 sizeof(m_pReadBuffer), 
                                                 NULL, // no object event callback
                                                 NULL, // driver object as parent
                                                 &pWdfBuffer); 
    //CHECK_HR(hr, "Failed to create the pre-allocaed WDF memory");
 
    if (hr == S_OK)
    {
        hr = m_pWDFDevice->CreateRequest(NULL, // no object event callback
                                         m_pWDFDevice, // device object as parent
                                         &pWdfReadRequest);
    }

    if (hr == S_OK)
    {
        // Format the read request
        m_pFileTarget->GetTargetFile(&pWdfFile);

        hr = m_pFileTarget->FormatRequestForRead(pWdfReadRequest,
                                                  pWdfFile,
                                                  pWdfBuffer,
                                                  NULL,  // no memory offset
                                                  NULL); // no device offset  

    }

    if (hr == S_OK)
    {
        this->QueryInterface(IID_PPV_ARGS(&pCompletionCallback));

        // Set the completion callback
        pWdfReadRequest->SetCompletionCallback(pCompletionCallback, NULL);

        hr = pWdfReadRequest->Send(m_pFileTarget, 0, 0);
    }

    if (FAILED(hr))
    {
        // Cleanup on failure
        if (pWdfReadRequest)
        {
            pWdfReadRequest->DeleteWdfObject();
        }
    }

    return hr;
}


/**
 * This method is called to dispatch an asynchronous write request to the RS232 I/O Target
 * with a completion callback
 */
HRESULT RS232Target::SendWriteRequest(
    BYTE*           pBuffer,
    size_t          cbBufferSize)
{
    CComPtr<IWDFDriver>                        pWdfDriver;
    CComPtr<IWDFFile>                          pWdfFile;
    CComPtr<IWDFMemory>                        pWdfBuffer;
    CComPtr<IWDFIoRequest>                     pWdfWriteRequest;
    CComPtr<IRequestCallbackRequestCompletion> pCompletionCallback;    

    HRESULT  hr = S_OK;

    if (pBuffer == NULL)
    {
        hr = E_POINTER;
        return hr;
    }

    if (m_pWDFDevice == NULL || m_pFileTarget == NULL)
    {
        hr = HRESULT_FROM_WIN32(ERROR_NOT_READY);
    }

    m_pWDFDevice->GetDriver(&pWdfDriver);
    m_pFileTarget->GetTargetFile(&pWdfFile);

    // Create the WDF memory buffer
    hr = pWdfDriver->CreatePreallocatedWdfMemory(pBuffer, 
                                                 cbBufferSize, 
                                                 NULL, // no object event callback
                                                 NULL, // driver object as parent
                                                 &pWdfBuffer); 
 
    if (hr == S_OK)
    {
        hr = m_pWDFDevice->CreateRequest(NULL, // no object event callback
                                         m_pWDFDevice, // device object as parent
                                         &pWdfWriteRequest);

    }

    if (hr == S_OK)
    {
        hr = m_pFileTarget->FormatRequestForWrite(pWdfWriteRequest,
                                                  pWdfFile,
                                                  pWdfBuffer,
                                                  NULL,  // no memory offset
                                                  NULL); // no device offset  

     }

    if (hr == S_OK)
    {
        this->QueryInterface(IID_PPV_ARGS(&pCompletionCallback));

        // Set the completion callback
        pWdfWriteRequest->SetCompletionCallback(pCompletionCallback, NULL);

        hr = pWdfWriteRequest->Send(m_pFileTarget, 0, 0);
    }
    
    if (FAILED(hr))
    {
        // Cleanup on failure
        if (pWdfWriteRequest)
        {
            pWdfWriteRequest->DeleteWdfObject();
        }
    }

    return hr;
}


/**
 * This callback method is called by UMDF on the completion of the asynchronous reads and writes
 */
void RS232Target::OnCompletion(
    IWDFIoRequest*                 pWdfRequest,
    IWDFIoTarget*                  pIoTarget,
    IWDFRequestCompletionParams*   pParams,
    PVOID                          pContext)
{
    UNREFERENCED_PARAMETER(pIoTarget);
    UNREFERENCED_PARAMETER(pContext);
    UNREFERENCED_PARAMETER(pParams);
    CComPtr<IWDFRequestCompletionParams>   pCompletionParams;
    HRESULT hr = S_OK;

    // Get the request completion status
    pWdfRequest->GetCompletionParams(&pCompletionParams);

    HRESULT hrStatus = pCompletionParams->GetCompletionStatus();
    WDF_REQUEST_TYPE RequestType = pCompletionParams->GetCompletedRequestType();

    if (RequestType == WdfRequestRead)
    {
        
        // Retrieve the data from the completed read request if successful
        if (SUCCEEDED(hrStatus) && m_pBaseDriver)
        {
            m_pBaseDriver->ProcessReadData(m_pReadBuffer, sizeof(m_pReadBuffer));
        }

        if (m_timer != NULL)
        {
            // If the sensor interval was reset, do a corresponding
            // reset of the timer interval
            if (m_Interval != m_pBaseDriver->m_Interval)

            {
                SetInternalTimer(m_pBaseDriver->m_Interval);
            }

            // The timer callback only fires if our interval elapses
            // without data being received.
            SetThreadpoolTimer(m_timer,
                           &m_FileDueTime,
                           0,
                           0);

            SensorState st = GetSensorState();
            if (st == SENSOR_STATE_NOT_AVAILABLE)
            {
                st = SENSOR_STATE_READY;

                // Set the current sensor state
                SetSensorState(st);

                // Post the sensor state-change event
                // This sensor is no longer available
                hr = m_pBaseDriver->m_spSensorCXT->PostStateChange(m_pBaseDriver->m_wszDeviceID, st);
            }
       }

        // Send another read request if the target is not stopped or removed
        if (IsReady())
        {
            hr = SendReadRequest();  
        }
    } 
    else if (RequestType == WdfRequestWrite)
    {
    }

    // Clean up the existing request
    pWdfRequest->DeleteWdfObject();
}

//
// Returns the current sensor state
//
SensorState RS232Target::GetSensorState()
{
    // Ensure that this value isn't currently being accessed by another thread
    CComCritSecLock<CComAutoCriticalSection> Lock(m_SensorStateCriticalSection);

    return m_state;
}

//
// Sets the current sensor state
//
VOID RS232Target::SetSensorState(SensorState st)
{
    // Ensure that this value isn't currently being accessed by another thread
    CComCritSecLock<CComAutoCriticalSection> Lock(m_SensorStateCriticalSection);

    m_state = st;
}

//
// Thread pool timer callback function
// This function posts the sensor state-change event
// with a state of SENSOR_STATE_NOT_AVAILABLE. (This corresponds
// to the sensor having been unplugged or having lost power.)
//
VOID RS232Target::OnTimerCallback()
{
    // Set the current sensor state
    SetSensorState(SENSOR_STATE_NOT_AVAILABLE);

    // Post the sensor state-change event
    // This sensor is no longer available
    m_pBaseDriver->m_spSensorCXT->PostStateChange(m_pBaseDriver->m_wszDeviceID, SENSOR_STATE_NOT_AVAILABLE);
}

//
// callback wrapper
//
static VOID WINAPI TimerCallback(
    PTP_CALLBACK_INSTANCE /*Instance*/,
    PVOID                 Parameter,
    PTP_TIMER             /*Timer*/
    )
{
    RS232Target * pTarget = static_cast<RS232Target*>(Parameter);
    
    pTarget->OnTimerCallback(); //Errors ignored.
}

//
// Thread pool timer creation function
//
VOID RS232Target::CreatePersistentWorkTimer()
{
    BOOL bRet = FALSE;
    PTP_TIMER_CALLBACK timercallback = &TimerCallback;
    TP_CALLBACK_ENVIRON CallBackEnviron;

    InitializeThreadpoolEnvironment(&CallBackEnviron);

    //
    // Create a custom, dedicated thread pool
    //
    m_pool = CreateThreadpool(NULL);

    if (NULL == m_pool) {
        return;
    }

    //
    // The thread pool is made persistent simply by setting
    // both the minimum and maximum threads to 1.
    //
    SetThreadpoolThreadMaximum(m_pool, 1);

    bRet = SetThreadpoolThreadMinimum(m_pool, 1);

    if (FALSE == bRet) {
        return;
    }

    //
    // Create a cleanup group for this thread pool
    //
    m_cleanupgroup = CreateThreadpoolCleanupGroup();

    if (NULL == m_cleanupgroup) {
        return;
    }

    //
    // Associate the callback environment with our thread pool
    //
    SetThreadpoolCallbackPool(&CallBackEnviron, m_pool);

    //
    // Associate the cleanup group with our thread pool
    //
    SetThreadpoolCallbackCleanupGroup(&CallBackEnviron,
                                      m_cleanupgroup,
                                      NULL);

    // 
    // We need to hold a reference to the "this" object
    // (We'll release this reference when the timer object is NULLed
    //
    AddRef();

    //
    // Create a timer with the same callback environment
    //
    m_timer = CreateThreadpoolTimer(timercallback,
                                  //NULL,
                                  this,
                                  &CallBackEnviron);


    if (NULL == m_timer) {
        Release();
        return;
    }


    //
    // Set the timer to fire in Interval+1 seconds
    //
    SetInternalTimer(m_pBaseDriver->m_Interval);

    return;
}

//
// SetInternalTimer sets the timer value consumed by the thread-pool
// timer callback. (Note that we add 1-second to the specified interval
// to prevent race conditions between the IoTarget thread and the
// thread-pool callback.)
// 
// iInterval is the timer-interval value specified in milliseconds
//
// If iInterval is 0, we set the callback interval to 2000 (or 2 seconds)
// which corresponds to the default interval in the sensor firmware.
//
VOID RS232Target::SetInternalTimer(int iInterval)
{
    int i=0;
    ULARGE_INTEGER ulDueTime;

    if (iInterval > 0)
    {
        m_Interval = iInterval;
        i = (int)(iInterval/1000);
        i++; //Buffer the interval by 1 second
    }
    else
    {
        m_Interval = 2000;
        i = (int)(m_Interval/1000);
        i++; //Buffer by 1 second
    }
    ulDueTime.QuadPart = (LONGLONG) -(i * 10 * 1000 * 1000);
    m_FileDueTime.dwHighDateTime = ulDueTime.HighPart;
    m_FileDueTime.dwLowDateTime  = ulDueTime.LowPart;
}

//
// Thread pool timer cleanup function
//
VOID RS232Target::CleanupPersistentWorkTimer()
{

    //
    // CloseThreadpoolCleanupGroupMembers also calls the cleanup
    // functions for all the individual objects in the specified
    // cleanup group.
    //
    CloseThreadpoolCleanupGroupMembers(m_cleanupgroup,
                                       FALSE,
                                       NULL);

    // Clean up the cleanup group
    CloseThreadpoolCleanupGroup(m_cleanupgroup);

    // Clean up the pool
    CloseThreadpool(m_pool);

    // 
    // We've been holding a reference to the "this" object
    // 
    //
    if (m_timer)
    {
        Release();
        m_timer = NULL;
    }

    return;

}


